//
//  MethodReceiver.m
//  TestThread
//
//  Created by Chris Burns on 10-07-08.
//  Copyright 2010 University of Calgary. All rights reserved.
//

#import "MethodReceiver.h"


@implementation MethodReceiver


@synthesize methods;

#pragma mark User Methods




/* initWithAddress: andPort - We initialize the socket, operationQueue and the timer but do not begin operation*/
- (id) initWithAddress:(NSString *)ipAddress andPort:(NSUInteger)port {
	
	if (self = [super init]) {
		
		socket = [[AsyncSocket alloc] init];
		operationQueue = [[NSOperationQueue alloc] init];
		methods = [[MethodCollection alloc] init];
		
		hostAddress = ipAddress; 
		hostPort = port; 
		
	}
	
	return self; 
	
}



/* startCommunication - This method connects to the serbver and begins the timer*/

- (void) startReceiving{
	
	
	socket.delegate = self; 
	
	[socket connectToHost:hostAddress onPort:hostPort error:nil];
	
	timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerReadMethod:) userInfo:nil repeats:YES];
	
	
}




#pragma mark Utility Methods



/* parseMethodCallAndSchedule - This method is responsible for creating an NSInvocationOperation and then  */

- (void) parseMethodCallAndSchedule:(NSString *)methodCall {
	

	//Create XML Document from the methodCall to begin the parsing process.
	CXMLDocument *methodRequestDocument = [[CXMLDocument alloc] initWithXMLString:methodCall options:0 error:nil];	


	//  Find the methodName in the XML document. This will be first and will only exist once.
	CXMLElement *methodNameXML = [[methodRequestDocument nodesForXPath:@"//methodName" error:nil] objectAtIndex:0];
	NSString *methodName = [methodNameXML stringValue];


	// Find the messageID which will be a GUID generated by the server. There will only exist one.
	CXMLElement *messageIDXML = [[methodRequestDocument nodesForXPath:@"//messageID" error:nil] objectAtIndex:0];
	NSString* messageID = [messageIDXML stringValue];



	// Since there can be many parameters we gather all of them together and cycle through each node.
	CXMLElement *methodParametersXMLAll = [[methodRequestDocument nodesForXPath:@"//methodParameters" error:nil] objectAtIndex:0];
	NSArray *methodParametersXML = [methodParametersXMLAll children];


	// We wish to handle arrays here 
	NSMutableArray *methodParameterValues = [[NSMutableArray alloc] init];


	for (CXMLNode *node in methodParametersXML) {
		
		
		//(TODO) Handle the parameter types (NSDouble, NSInteger, NSData etc here) ... 
		
		[methodParameterValues addObject:[node stringValue]];
		
	}

	
	// The last parameter of any valid remote call method is always the messageID. 
	[methodParameterValues addObject:messageID];


	
	// methodCall is the result of creating the invocation and then it is scheduled into the operationQueue so it will run concurrently.
	NSInvocationOperation *methodInvocationOperation = [self createInvocationForMethod:methodName andParameters:methodParameterValues withTarget:methods];
	
	
	
	[operationQueue addOperation:methodInvocationOperation];
	

}

/* creatInvocationForMethod: andParameters: withTarget: - This method creates and NSInvocationOperation for each of the MethodCalls 
 * it loads the code in via a selector and then add's each of the arguments provided. No primitive types are allowed. Then the 
 * invocation is returned to be scheduled by the calling method. 
 
 
 
 */
- (NSInvocationOperation *) createInvocationForMethod:(NSString *) methodName andParameters: (NSMutableArray *) params withTarget: (id) target{
	
	
	
	SEL methodSelector = NSSelectorFromString(methodName);
	
	if (! [target respondsToSelector:methodSelector]) {
		
		//(TODO) Raise an NSException? Skip it? Return an exception back to the server? But don't run...
		return nil;  //To avoid exceptions when we send messages later.  
		
	}
	
	//(
	NSMethodSignature *methodSig = [target methodSignatureForSelector:methodSelector];
	NSInvocation *methodInvocation = [NSInvocation invocationWithMethodSignature:methodSig];
	
	
	//As we add parameters to methodInvocation we must be aware that the first two parameters (hidden) that are passed in are self and -cmd
	//This code based on code found at http://excitabyte.wordpress.com/2009/07/07/spawning-threads-using-selectors-with-multiple-parameters/ */
	[methodInvocation setTarget:target]; //Index at 0 
	[methodInvocation setSelector:methodSelector]; //Index at 1
	
	
	//Add parameters into the method invocation.
	for (int i = 0; i<[params count]; i++) {
		
		
		id value = [params objectAtIndex:i];
		
		[methodInvocation setArgument:&value atIndex:(i+2)]; //i+2 is added as an offset for the two hidden params, setArgument requires a pointer
		
		
		
	}
	
	/*(TODO) handling the memory problems related to this. Since the parameters are passed via pointer they are not retained. Thus
	 * a special call must be made to retain them. This */
	 
	[methodInvocation retainArguments]; 

	
	return [[NSInvocationOperation alloc] initWithInvocation:methodInvocation];
	
	
	
}



/* timerReadMethod - This is the method called on interval by the timer. It simple calls a new "read" method on the socket which will not continously 
 * check for updates without it. */

- (void) timerReadMethod: (NSTimer *) theTimer {
	
	[socket readDataWithTimeout:1 tag:0];

}




/* dealloc - We send release messages to all class variables and send a dealloc message to the super class*/
- (void) dealloc {
	
	[socket release];
	[timer release];
	[operationQueue release];
	[methods release]; 
	
	
	[super dealloc];
	
	
	
}











#pragma mark AsycSocket Delegate/Utility Methods

/* onSocketWillConnect: - This method (not a delegate) simply return TRUE to indicate we intend to connect to the socket */
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock 
{
	return TRUE;
	
}


/* onSocket: didConnectToHost: port: - This delegate method is called whenever the socket succeeds in connecting to the host */
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port {
	
	
	NSLog(@"Did Connect To Host"); 
	isConnectedToHost = YES;

}


/* onSocket: willDisconnectWithError: - This delegate method is called whenever the socket disconnects. */
-(void) onSocket:(AsyncSocket *) sock willDisconnectWithError:(NSError *) error {
	
	
	isConnectedToHost = NO;
	

}


/* onSocket: didReadData: withTag: - This delegate method is called whenever the socket successfully reads some data from the socket 
 * it is important to know what this data is not guaranteed (and is rarely likely) to contain a single MethodCall message. Thus it must be parsed 
 * based on this assumption. */

- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
	
	NSString *fullString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
	NSArray *messages = [fullString componentsSeparatedByString:@"\n"];	//Newlines are what seperates the messages.

	
	for (NSString * s in messages) {
		
		if ([s length] == 0) { break;} //The last message in the list is an "" empty character.

		[self parseMethodCallAndSchedule:s];
		
		NSLog(@" Message %@", s); 
		
		
		
		

	}
	
}


@end
